#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "v4l2_smart.h"

#include <Ecore.h>
#include <Evas.h>

#include <gst/gst.h>
#include <gst/interfaces/videoorientation.h>

#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

#define E_SMART_OBJ_GET(smart, o) \
  { \
     if (!o) return; \
     smart = evas_object_smart_data_get(o); \
     if (!smart) return; \
  }

#define E_SMART_OBJ_GET_RETURN(smart, o, ret) \
  { \
     if (!o) return ret; \
     smart = evas_object_smart_data_get(o); \
     if (!smart) return ret; \
  }

typedef struct _V4l2_Smart_Data V4l2_Smart_Data;

struct _V4l2_Smart_Data
{
   Evas_Object *obj;
   pthread_mutex_t mutex;
   Evas_Bool play;
   Evas_Colorspace cspace;
   void *buffer;
   unsigned int buffer_size;
   int w;
   int h;
   int fd_read;
   int fd_write;
   Ecore_Fd_Handler *fd_handler;
   GstElement *pipeline;
   Evas_Bool hflip;
   Evas_Bool vflip;
};

static void _v4l2_smart_add(Evas_Object * obj);
static void _v4l2_smart_del(Evas_Object * obj);
static void _v4l2_smart_move(Evas_Object * obj, Evas_Coord x, Evas_Coord y);
static void _v4l2_smart_resize(Evas_Object * obj, Evas_Coord w, Evas_Coord h);
static void _v4l2_smart_show(Evas_Object * obj);
static void _v4l2_smart_hide(Evas_Object * obj);
static void _v4l2_smart_color_set(Evas_Object * obj, int r, int g, int b, int a);
static void _v4l2_smart_clip_set(Evas_Object * obj, Evas_Object * clip);
static void _v4l2_smart_clip_unset(Evas_Object * obj);

static Evas_Bool _v4l2_smart_play(V4l2_Smart_Data * vsd);
static void _v4l2_smart_stop(V4l2_Smart_Data * vsd);
static void _v4l2_smart_hflip_set(V4l2_Smart_Data * vsd, Evas_Bool flip);
static void _v4l2_smart_vflip_set(V4l2_Smart_Data * vsd, Evas_Bool flip);
static void _v4l2_smart_pixels_get(void * data, Evas_Object * obj);
static void _v4l2_smart_yuv_data_get(unsigned char *buffer, int w, int h, unsigned char **ydata, unsigned char **udata, unsigned char **vdata);
static void _v4l2_smart_streamer_handoff(GstElement * element, GstBuffer * buffer, GstPad * pad, V4l2_Smart_Data * vsd);
static int _v4l2_smart_fd_active(void * data, Ecore_Fd_Handler * fdh);

static Evas_Smart *_v4l2_smart = NULL;

EAPI Evas_Object *
v4l2_object_add(Evas *evas)
{
   if (!_v4l2_smart)
     {
	static const Evas_Smart_Class sc =
	  {
	     "v4l2_object",
	       EVAS_SMART_CLASS_VERSION,
	       _v4l2_smart_add,
	       _v4l2_smart_del,
	       _v4l2_smart_move,
	       _v4l2_smart_resize,
	       _v4l2_smart_show,
	       _v4l2_smart_hide,
	       _v4l2_smart_color_set,
	       _v4l2_smart_clip_set,
	       _v4l2_smart_clip_unset,
	       NULL
	  };
        _v4l2_smart = evas_smart_class_new(&sc);
     }
   return evas_object_smart_add(evas, _v4l2_smart);
}

EAPI Evas_Bool
v4l2_object_play_set(Evas_Object *obj, Evas_Bool play)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, 0);
   if (((play) && (vsd->play)) || ((!play) && (!vsd->play))) return 1;

   vsd->play = play;
   if (vsd->play)
     {
	if (!_v4l2_smart_play(vsd))
	  {
	     vsd->play = 0;
	     return 0;
	  }

	/* make sure h/v flip is properly set */
	_v4l2_smart_hflip_set(vsd, vsd->hflip);
	_v4l2_smart_vflip_set(vsd, vsd->vflip);
     }
   else
     _v4l2_smart_stop(vsd);

   return 1;
}

EAPI Evas_Bool
v4l2_object_play_get(Evas_Object *obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, 0);
   return vsd->play;
}

EAPI void
v4l2_object_colorspace_set(Evas_Object *obj, Evas_Colorspace cspace)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   if (vsd->cspace == cspace) return;
   vsd->cspace = cspace;
   if (vsd->play)
     {
	v4l2_object_play_set(obj, 0);
	evas_object_image_colorspace_set(vsd->obj, cspace);
	v4l2_object_play_set(obj, 1);
     }
   else
     evas_object_image_colorspace_set(vsd->obj, cspace);
}

EAPI Evas_Colorspace
v4l2_object_colorspace_get(Evas_Object *obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, EVAS_COLORSPACE_ARGB8888);
   return vsd->cspace;
}

EAPI void *
v4l2_object_data_get(Evas_Object *obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, NULL);
   return evas_object_image_data_get(vsd->obj, 0);
}

EAPI void
v4l2_object_size_get(Evas_Object *obj, int *w, int *h)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_image_size_get(vsd->obj, w, h);
}

EAPI void
v4l2_object_hflip_set(Evas_Object *obj, Evas_Bool flip)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   if (vsd->hflip == flip) return;
   vsd->hflip = flip;
   if (vsd->play) _v4l2_smart_hflip_set(vsd, flip);
}

EAPI Evas_Bool
v4l2_object_hflip_get(Evas_Object *obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, 0);
   return vsd->hflip;
}

EAPI void
v4l2_object_vflip_set(Evas_Object *obj, Evas_Bool flip)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   if (vsd->vflip == flip) return;
   vsd->vflip = flip;
   if (vsd->play) _v4l2_smart_vflip_set(vsd, flip);
}

EAPI Evas_Bool
v4l2_object_vflip_get(Evas_Object *obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET_RETURN(vsd, obj, 0);
   return vsd->vflip;
}

/* Private routines */
static void
_v4l2_smart_add(Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;

   vsd = calloc(1, sizeof(V4l2_Smart_Data));
   if (!vsd) return;

   vsd->obj = evas_object_image_add(evas_object_evas_get(obj));
   evas_object_image_alpha_set(vsd->obj, 0);
   evas_object_image_size_set(vsd->obj, 1, 1);
   evas_object_image_pixels_get_callback_set(vsd->obj, _v4l2_smart_pixels_get, vsd);
   evas_object_smart_member_add(vsd->obj, obj);
   evas_object_smart_data_set(obj, vsd);
   vsd->cspace = evas_object_image_colorspace_get(vsd->obj);
}

static void
_v4l2_smart_del(Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   v4l2_object_play_set(obj, 0);
   evas_object_del(vsd->obj);
   free(vsd);
}

static void
_v4l2_smart_move(Evas_Object * obj, Evas_Coord x, Evas_Coord y)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_move(vsd->obj, x, y);
}

static void
_v4l2_smart_resize(Evas_Object * obj, Evas_Coord w, Evas_Coord h)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_image_fill_set(vsd->obj, 0, 0, w, h);
   evas_object_resize(vsd->obj, w, h);
}

static void
_v4l2_smart_show(Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_show(vsd->obj);
}

static void
_v4l2_smart_hide(Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_hide(vsd->obj);
}

static void
_v4l2_smart_color_set(Evas_Object * obj, int r, int g, int b, int a)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_color_set(vsd->obj, r, g, b, a);
}

static void
_v4l2_smart_clip_set(Evas_Object * obj, Evas_Object * clip)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_clip_set(vsd->obj, clip);
}

static void
_v4l2_smart_clip_unset(Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;

   E_SMART_OBJ_GET(vsd, obj);
   evas_object_clip_unset(vsd->obj);
}

static Evas_Bool
_v4l2_smart_play(V4l2_Smart_Data * vsd)
{
   int fds[2];
   GstElement *pipeline;
   GstElement *v4l2src;
   GstElement *ffmpegcolorspace;
   GstElement *sink;
   GstCaps *caps;

   /* create the file descriptors */
   if (pipe(fds) == -1) return 0;

   pthread_mutex_init(&vsd->mutex, NULL);
   vsd->fd_read = fds[0];
   vsd->fd_write = fds[1];
   fcntl(vsd->fd_read, F_SETFL, O_NONBLOCK);
   vsd->fd_handler = ecore_main_fd_handler_add(vsd->fd_read,
					       ECORE_FD_READ,
					       _v4l2_smart_fd_active,
					       vsd,
					       NULL, NULL);
   ecore_main_fd_handler_active_set(vsd->fd_handler, ECORE_FD_READ);

   /* create the gstreamer pipeline */
   pipeline = gst_pipeline_new("pipeline");
   vsd->pipeline = pipeline;

   v4l2src = gst_element_factory_make("v4l2src", "v4l2src");
   if (!v4l2src) goto failure;
   ffmpegcolorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace");
   if (!ffmpegcolorspace) goto failure_ffmpegcolorspace;
   sink = gst_element_factory_make("fakesink", "sink");
   if (!sink) goto failure_sink;
   g_object_set(G_OBJECT(sink), "sync", FALSE, NULL);

   gst_bin_add_many(GST_BIN(pipeline), v4l2src, ffmpegcolorspace, sink, NULL);

   caps = gst_caps_new_simple("video/x-raw-yuv",
			      "width", G_TYPE_INT, 320,
			      "height", G_TYPE_INT, 240,
			      NULL);
   if (!gst_element_link_filtered(v4l2src, ffmpegcolorspace, caps))
     {
	gst_caps_unref(caps);
	goto failure;
     }
   gst_caps_unref(caps);

   if (vsd->cspace == EVAS_COLORSPACE_YCBCR422P601_PL)
     {
	caps = gst_caps_new_simple("video/x-raw-yuv",
				   "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),
				   NULL);
     }
   else if (vsd->cspace == EVAS_COLORSPACE_RGB565_A5P)
     {
	caps = gst_caps_new_simple("video/x-raw-rgb",
				   "bpp", G_TYPE_INT, 16,
				   "depth", G_TYPE_INT, 16,
				   "red_mask", G_TYPE_INT, 0xf800,
				   "green_mask", G_TYPE_INT, 0x7e0,
				   "blue_mask", G_TYPE_INT, 0x1f,
				   NULL);
     }
   else if (vsd->cspace == EVAS_COLORSPACE_ARGB8888)
     {
	caps = gst_caps_new_simple("video/x-raw-rgb",
				   "bpp", G_TYPE_INT, 32,
				   "depth", G_TYPE_INT, 32,
				   "red_mask", G_TYPE_INT, 0xff000000,
				   "green_mask", G_TYPE_INT, 0xff0000,
				   "blue_mask", G_TYPE_INT, 0xff00,
				   "alpha_mask", G_TYPE_INT, 0xff,
				   "endianness", G_TYPE_INT, G_BIG_ENDIAN,
				   NULL);
     }

   if (!gst_element_link_filtered(ffmpegcolorspace, sink, caps))
     {
	gst_caps_unref(caps);
	goto failure;
     }
   gst_caps_unref(caps);

   g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL);
   g_signal_connect(sink, "handoff",
		    G_CALLBACK (_v4l2_smart_streamer_handoff), vsd);
   gst_element_set_state(pipeline, GST_STATE_PLAYING);

   return 1;

failure_sink:
   gst_object_unref(ffmpegcolorspace);
failure_ffmpegcolorspace:
   gst_object_unref(v4l2src);
failure:
   _v4l2_smart_stop(vsd);
   return 0;
}

static void
_v4l2_smart_stop(V4l2_Smart_Data * vsd)
{
   gst_element_set_state(vsd->pipeline, GST_STATE_NULL);
   gst_object_unref(vsd->pipeline);
   ecore_main_fd_handler_del(vsd->fd_handler);
   close(vsd->fd_read);
   close(vsd->fd_write);
   if (vsd->buffer)
     {
	free(vsd->buffer);
	vsd->buffer = NULL;
     }
   pthread_mutex_destroy(&vsd->mutex);
}

static void
_v4l2_smart_hflip_set(V4l2_Smart_Data * vsd, Evas_Bool flip)
{
   GstElement *v4l2src;

   v4l2src = gst_bin_get_by_name(GST_BIN(vsd->pipeline), "v4l2src");
   gst_video_orientation_set_hflip(GST_VIDEO_ORIENTATION(v4l2src), flip);
}

static void
_v4l2_smart_vflip_set(V4l2_Smart_Data * vsd, Evas_Bool flip)
{
   GstElement *v4l2src;

   v4l2src = gst_bin_get_by_name(GST_BIN(vsd->pipeline), "v4l2src");
   gst_video_orientation_set_vflip(GST_VIDEO_ORIENTATION(v4l2src), flip);
}

static void
_v4l2_smart_streamer_handoff(GstElement * element,
			     GstBuffer * buffer,
			     GstPad * pad,
			     V4l2_Smart_Data * vsd)
{
   void *buf[1];
   unsigned int buffer_size;

   pthread_mutex_lock(&vsd->mutex);

   buffer_size = GST_BUFFER_SIZE(buffer);
   if (!vsd->buffer || vsd->buffer_size != buffer_size)
     {
	GstCaps *caps;
	GstStructure *structure;

	if (vsd->buffer) free(vsd->buffer);
	vsd->buffer = (void*) malloc(buffer_size * sizeof(void));
	vsd->buffer_size = buffer_size;

	caps = gst_buffer_get_caps(buffer);
	structure = gst_caps_get_structure(caps, 0);
	gst_structure_get_int(structure, "width", &vsd->w);
	gst_structure_get_int(structure, "height", &vsd->h);
	gst_caps_unref(caps);
     }

   memcpy(vsd->buffer, GST_BUFFER_DATA(buffer), buffer_size);
   write(vsd->fd_write, buf, sizeof(buf));

   pthread_mutex_unlock(&vsd->mutex);
}

static void
_v4l2_smart_pixels_get(void * data, Evas_Object * obj)
{
   V4l2_Smart_Data *vsd;
   int iw, ih;

   vsd = data;

   pthread_mutex_lock(&vsd->mutex);

   evas_object_image_size_get(obj, &iw, &ih);
   if ((vsd->w != iw) || (vsd->h != ih))
     {
	evas_object_image_size_set(obj, vsd->w, vsd->h);
	iw = vsd->w;
	ih = vsd->h;
     }

   if (vsd->cspace == EVAS_COLORSPACE_YCBCR422P601_PL)
     {
	unsigned char **image_data;

	image_data = evas_object_image_data_get(obj, 1);
	if (image_data)
	  {
	     _v4l2_smart_yuv_data_get(vsd->buffer, iw, ih,
				      image_data,
				      &image_data[ih],
				      &image_data[ih + (ih / 2)]);
	     evas_object_image_data_update_add(obj, 0, 0, iw, ih);
	  }
     }
   else if (vsd->cspace == EVAS_COLORSPACE_RGB565_A5P ||
	    vsd->cspace == EVAS_COLORSPACE_ARGB8888)
     {
	evas_object_image_data_set(obj, vsd->buffer);
     }

   evas_object_image_pixels_dirty_set(obj, 0);

   pthread_mutex_unlock(&vsd->mutex);
}

static void
_v4l2_smart_yuv_data_get(unsigned char *buffer,
		   int w,
		   int h,
		   unsigned char **ydata,
		   unsigned char **udata,
		   unsigned char **vdata)
{
   int i;

   for (i = 0; i < h; i++)
     ydata[i] = &buffer[i * w];
   for (i = 0; i < (h / 2); i++)
     udata[i] = &buffer[h * w + i * (w / 2) ];
   for (i = 0; i < (h / 2); i++)
     vdata[i] = &buffer[h * w + h * (w /4) + i * (w / 2)];
}

static int
_v4l2_smart_fd_active(void * data, Ecore_Fd_Handler * fdh)
{
   V4l2_Smart_Data *vsd;
   int fd;
   int len;
   void *buf[1];

   vsd = data;
   fd = ecore_main_fd_handler_fd_get(fdh);
   while ((len = read(fd, buf, sizeof(buf))) > 0)
     {
	if (len == sizeof(buf))
	  {
	     evas_object_image_pixels_dirty_set(vsd->obj, 1);
	  }
     }

   return 1;
}
